#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
Simplified BSD License (BSD)
Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 01-Nov-09 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using System.Drawing;
using System.Drawing.Text;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.ComponentModel;

using Thn;
using Thn.Interface.Vcl.Themes;
#endregion

namespace Thn.Interface.Vcl
{
    /// <summary>
    /// Base class for visual controls.
    /// </summary>
    /// <remarks>This class supports transparency by double-buffering and by capturing all underlying
    /// controls' images.</remarks>
#if DEBUG
    public
#else
    internal
#endif
 abstract class ForcedPaintingControl : Control
                            , IControl
                            , ISupportTransparency
                            , ISupportTheme
    {
        #region Fields
        /// <summary>
        /// Whether method OnPaint should return immediately without painting.
        /// </summary>
        bool mSkipPaint = false;
        /// <summary>
        /// Get background image again (for transparency effect)
        /// </summary>
        bool mRedrawBackground = true;

        /// <summary>
        /// Whether this control needs to be rendered again.
        /// </summary>
        bool mRenderRequired = true;
        /// <summary>
        /// Whether this controls needs to be
        /// </summary>
        bool mLayoutRequired = true;
        #endregion

        #region Properties

        #region Design Mode
        bool mDesignMode = false;
        /// <summary>
        /// Checks whether this control is in design mode.
        /// <para>
        /// Note: this property is assigned via Thn.Interface.Vcl.UI.DesignMode
        /// </para>
        /// </summary>
        public new bool DesignMode
        {
            get { return mDesignMode; }
        }
        #endregion

        #region Transparent
        private bool mTransparent;
        /// <summary>
        /// Gets/Sets whether this control can show its parent's painting.
        /// </summary>
        [Browsable(true), DefaultValue(false), Category("Appearance"), Description("Whether this control can show its parent's painting")]
        public bool Transparent
        {
            get { return mTransparent; }
            set
            {
                mTransparent = value;
                Invalidate();
            }
        }
        #endregion

        #region Cache Background
        /// <summary>
        /// Whether this control should cache its background for faster transparency painting.
        /// </summary>
        private bool mCacheBackground;
        /// <summary>
        /// Gets/Sets whether this control should cache its background for faster transparency painting.
        /// </summary>
        [Browsable(true), DefaultValue(false), Category("Appearance"), Description("Save background for faster transparency painting.")]
        public bool CacheBackground
        {
            get { return mCacheBackground; }
            set
            {
                mCacheBackground = value;
                Invalidate();
            }
        }
        #endregion

        #region Quality
        private RenderQuality mQuality = RenderQuality.Normal;
        /// <summary>
        /// Gets/Sets the renderation quality.
        /// </summary>
        [Browsable(true), DefaultValue(RenderQuality.Normal), Category("Appearance"), Description("Quality of renderation")]
        public RenderQuality Quality
        {
            get { return mQuality; }
            set
            {
                mQuality = value;
                Invalidate();
            }
        }
        #endregion

        #region Text
        private string mText = "";
        /// <summary>
        /// Gets/Sets the display text.
        /// </summary>
        [Browsable(true), DefaultValue(""), Category("Value"), Description("Displayed text"), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
        public override string Text
        {
            get
            {
                return mText;
            }
            set
            {
                if (mText != value)
                {
                    mText = value;
                    Invalidate();
                    OnTextChanged(EventArgs.Empty);
                }
            }
        }
        #endregion

        #region Theme
        private string mTheme = "";
        /// <summary>
        /// Gets/Sets ID of the theme to be applied for this control. 
        /// </summary>
        [Browsable(true), DefaultValue(""), Category("Appearance"), Description("ID of the theme to be applied")]
        public string Theme
        {
            get { return mTheme; }
            set
            {
                mTheme = value;
                ApplyTheme(mTheme);
                Invalidate();
            }
        }
        #endregion

        #region Effective Theme
        private ITheme mEffectiveTheme;
        /// <summary>
        /// Gets the theme currently applied to this control.
        /// </summary>
        public ITheme EffectiveTheme
        {
            get
            {
                //ensure the effective theme is not null
                if (mEffectiveTheme == null) mEffectiveTheme = UI.Themes.Current;
                #region Defensive tracing
                else
                {
                    //already has a theme
                }
                #endregion
                return mEffectiveTheme;
            }
        }
        #endregion

        #region Renderer
        /// <summary>
        /// The custom renderer set by user.
        /// </summary>
        private IControlRenderer mRenderer = null;
        /// <summary>
        /// Gets/Sets the renderer used for rendering this control.
        /// </summary>
        [Browsable(false), DefaultValue(null), Category("Appearance"), Description("The renderer used for rendering this control")]
        public IControlRenderer Renderer
        {
            get { return mRenderer; }
            set
            {
                mRenderer = value;
                Invalidate();
            }
        }
        #endregion

        #endregion

        #region On Get Render Context
        /// <summary>
        /// Get reference to the renderation context which is used by <see cref="IControlRenderer"/>.
        /// </summary>
        protected abstract RenderContext OnGetRenderContext(Graphics canvas);
        #endregion

        #region Get Render Context
        /// <summary>
        /// Get reference to the renderation context which is used by <see cref="IControlRenderer"/>.
        /// </summary>
        RenderContext GetRenderContext(Graphics canvas)
        {
            RenderContext result = OnGetRenderContext(canvas);

            return result;
        }
        #endregion

        #region Drawing Routines

        #region On Render Content
        /// <summary>
        /// Paint the control's content (text, value, data, etc.) onto canvas.
        /// </summary>
        /// <param name="canvas">The canvas on which to paint the control.</param>
        /// <param name="renderer">The optional renderer for the selected theme.</param>
        /// <param name="context">The current drawing context.</param>
        protected virtual void OnRenderContent(Graphics canvas, IControlRenderer renderer, RenderContext context)
        {
            if (renderer != null)
            {
                renderer.RenderContent(this, canvas, context);
            }
            #region Defensive tracing
            else
            {
            }
            #endregion
        }
        #endregion

        #region On Render Visual
        /// <summary>
        /// Paint the control's visual stuff (border, background, etc.) onto canvas.
        /// </summary>
        /// <param name="canvas">The canvas on which to paint the control.</param>
        /// <param name="renderer">The optional renderer for the selected theme.</param>
        /// <param name="context">The current drawing context.</param>
        protected virtual void OnRenderVisual(Graphics canvas, IControlRenderer renderer, RenderContext context)
        {
            if (renderer != null)
            {
                renderer.RenderVisual(this, canvas, context);
            }
            #region Defensive tracing
            else
            {
                canvas.Clear(this.BackColor);
                canvas.DrawString("ERROR: NO RENDERER FOUND!", Font, Brushes.Red, 5, 5);
                Rectangle r = new Rectangle(5, 25, Width - 10, Height - 25);
                StringFormat fmt = new StringFormat();
                fmt.Trimming = StringTrimming.EllipsisCharacter;
                canvas.DrawString("Descendants of ControlBase should either override OnRenderXXX or provide a renderer", Font, Brushes.Red, r, fmt);
            }
            #endregion
        }
        #endregion

        #region Draw Transparency
        /// <summary>
        /// Paint this control's parent to mimic the transparency effects.
        /// </summary>
        void DrawTransparency(Graphics canvas)
        {
            if (mCacheBackground)
            {
                //mRedrawBackground = true;
                if (mRedrawBackground)
                {
                    GetBackground(mBackgroundCanvas);

                    //suppress further drawing
                    mRedrawBackground = false;
                }
                #region Defensive tracing
                else
                {
                    //background is cached
                }
                #endregion

                canvas.DrawImageUnscaled(mBackgroundBitmap, 0, 0);
            }
            else
            {
                GetBackground(canvas);
            }
        }
        #endregion

        #region On Paint
        /// <summary>
        /// Paint this control. Descendants of this class should NOT override this method.
        /// </summary>
        protected override void OnPaint(PaintEventArgs e)
        {
            Graphics g = null;

            #region Pre-conditions

            //immediately return, do not paint
            if ((mSkipPaint) || (Width < 1) || (Height < 1)) return;
            #region Defensive tracing
            else
            {
                //continue painting.
            }
            #endregion

            #endregion

            #region Perform layout
            if (mLayoutRequired)
            {
                CalculateLayout();
            }
            #region Defensive tracing
            else
            {
                //layout calculation is not required, continue painting.
            }
            #endregion

            #endregion

            #region Ensure buffers are setup
            if (mBufferPreparationRequired)
            {
                DisposeBuffers();
                PrepareBuffers();

                //suppress further allocation
                mBufferPreparationRequired = false;
            }
            #region Defensive tracing
            else
            {
                //no need to re-create buffers.
            }
            #endregion

            #endregion

            g = mControlCanvas;

            #region Render to buffer

            if (mRenderRequired)
            {
                #region Apply renderation quality
                switch (mQuality)
                {
                    case RenderQuality.Quality:
                        g.SmoothingMode = SmoothingMode.HighQuality;
                        g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                        g.CompositingQuality = CompositingQuality.HighQuality;
                        g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
                        break;

                    case RenderQuality.Normal:
                        g.SmoothingMode = SmoothingMode.AntiAlias;
                        g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                        g.CompositingQuality = CompositingQuality.Default;
                        g.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
                        break;

                    case RenderQuality.Speed:
                        g.SmoothingMode = SmoothingMode.HighSpeed;
                        g.PixelOffsetMode = PixelOffsetMode.Default;
                        g.CompositingQuality = CompositingQuality.HighSpeed;
                        g.TextRenderingHint = TextRenderingHint.SystemDefault;
                        break;

                    default:
                        break;
                }
                #endregion

                //obtain renderer
                IControlRenderer renderer = null;
                if (mRenderer != null)
                {//has custom renderer
                    renderer = mRenderer;
                }
                else if (mThemeRenderer != null)
                {//has theme renderer
                    renderer = mThemeRenderer;
                }
                else
                {//no theme at all, try to use the default theme
                    mThemeRenderer = UI.Themes.Current.FindRenderer(GetType());
                    renderer = mThemeRenderer;
                }

                #region Render transparency

                if (mTransparent)
                {
                    DrawTransparency(g);
                }
                #region Defensive tracing
                else
                {
                    //transparency not required
                }
                #endregion

                #endregion

                //create context
                RenderContext context = GetRenderContext(g);

                //render
                OnRenderVisual(g, renderer, context);
                OnRenderContent(g, renderer, context);

                //suppress further render
                mRenderRequired = false;
            }
            else
            {
                //render is not required
            }

            #endregion

            #region Blast to screen

            //render the control buffer
            e.Graphics.DrawImageUnscaled(mControlBitmap, 0, 0);

            #endregion
        }
        #endregion

        #region On Paint Background
        /// <summary>
        /// Paint this control's background.
        /// </summary>
        protected override void OnPaintBackground(PaintEventArgs pevent)
        {
            Graphics canvas = pevent.Graphics;

            if (!mTransparent)
            {
                //fill in the background
                if (this.Parent != null)
                {
                    canvas.Clear(this.Parent.BackColor);
                }
                else
                {
                    canvas.Clear(this.BackColor);
                }
            }
            #region Defensive tracing
            else
            {
                //transparent mode, do not automatically fill in the background
            }
            #endregion
        }
        #endregion

        #region Get Background
        Rectangle rSelf;

        /// <summary>
        /// Capture the background of this control by invalidating underlying controls
        /// </summary>
        void GetBackground(Graphics canvas)
        {
            mSkipPaint = true;

            bool slowCapture = true;

            if (Parent is IBufferProvider)
            {
                IBufferProvider bp = (IBufferProvider)Parent;
                if (bp.HasBuffer)
                {
                    Bitmap buffer = bp.Background;
                    if (buffer != null)
                    {
                        slowCapture = false;
                        Rectangle rDest = new Rectangle(0, 0, Width, Height);
                        Rectangle rSrc = new Rectangle(Left, Top, Width, Height);
                        canvas.DrawImage(buffer, rDest, rSrc, GraphicsUnit.Pixel);
                    }
                }
            }

            if (slowCapture)
            {
                //invoke parent's OnPaint
                Control con = Parent;
                if (con != null)
                {
                    int dx = -Left;
                    int dy = -Top;
                    rSelf = new Rectangle(Left, Top, Width, Height);
                    Rectangle rIntersect = con.ClientRectangle;
                    rIntersect.Intersect(rSelf);

                    DrawControl(con, canvas, ToScreen(con, rIntersect));
                }
            }

            //return result
            mSkipPaint = false;
        }

        #region ToScreen / FromScreen
        Rectangle ToScreen(Control con, Rectangle r)
        {
            return new Rectangle(con.PointToScreen(r.Location), r.Size);
        }

        Rectangle ToClient(Control con, Rectangle r)
        {
            return new Rectangle(con.PointToClient(r.Location), r.Size);
        }
        #endregion

        #region Draw Control
        void DrawControl(Control con, Graphics canvas, Rectangle clipRect)
        {
            if (con != this)
            {
                //create a buffer bitmap to draw onto
                Bitmap bmp = new Bitmap(con.Width, con.Height, PixelFormat.Format32bppPArgb);
                Graphics g = Graphics.FromImage(bmp);
                PaintEventArgs pea = null;
                Rectangle clientClipRect = ToClient(con, clipRect);
                pea = new PaintEventArgs(g, clientClipRect);
                g.SetClip(clientClipRect);

                //draw control on buffer
                InvokePaintBackground(con, pea);
                InvokePaint(con, pea);

                //draw children on buffer
                if (con.Controls.Count > 0) DrawControlChildren(con, g, clipRect);

                //blast to my canvas
                Rectangle rSource = clientClipRect;
                Rectangle rDest = ToClient(this, clipRect);
                if (con != this.Parent) rDest = ToClient(con.Parent, clipRect);
                canvas.DrawImage(bmp, rDest, rSource.Left, rSource.Top, rSource.Width, rSource.Height, GraphicsUnit.Pixel);

                //dipose stuff
                g.Dispose();
                g = null;
                bmp.Dispose();
                bmp = null;
                pea.Dispose();
                pea = null;
            }
        }
        #endregion

        #region Draw Control Children
        void DrawControlChildren(Control con, Graphics canvas, Rectangle clipRect)
        {
            for (int i = con.Controls.Count - 1; i >= 0; i--)
            {
                Control child = con.Controls[i];
                if ((con != this) && (child != this))
                {
                    Rectangle rIntersect = ToScreen(con, new Rectangle(child.Left, child.Top, child.Width, child.Height));
                    rIntersect.Intersect(clipRect);
                    if (!rIntersect.IsEmpty)
                    {
                        DrawControl(child, canvas, rIntersect);
                    }
                }
                else break;
            }
        }
        #endregion

        #endregion

        #region Invalidate Renderation
        /// <summary>
        /// Requests visual and content renderation to be performed again at next painting.
        /// </summary>
        public void InvalidateRenderation()
        {
            mRenderRequired = true;
            Invalidate();
        }
        #endregion

        #region Invalidate - Threadsafe version
        /// <summary>
        /// Event handler for Invalidate.
        /// </summary>
        EventHandler mInvalidateHandler = null;

        void DoInvalidate(object sender, EventArgs e)
        {
            base.Invalidate();
        }

        /// <summary>
        /// A thread-safe version of invalidate.
        /// </summary>
        public new void Invalidate()
        {
            try
            {
                mRenderRequired = true;
                if (InvokeRequired) Invoke(mInvalidateHandler, this, EventArgs.Empty);
                else DoInvalidate(null, null);
            }
            catch
            {
                //deliberately ignore any errors that might occur
            }
        }
        #endregion

        #endregion

        #region Theming
        /// <summary>
        /// The renderer for this control based on current theme settings.
        /// </summary>
        private IControlRenderer mThemeRenderer = null;

        #region Apply Theme
        void ApplyTheme(string themeId)
        {
            //find renderer based on theme ID
            if ((themeId != null) && (themeId != ""))
            {
                if (UI.Themes.Contains(themeId))
                {
                    mEffectiveTheme = UI.Themes[themeId];
                    mThemeRenderer = mEffectiveTheme.FindRenderer(GetType());
                }
                else
                {
                    mEffectiveTheme = UI.Themes.Current;
                    mThemeRenderer = null;
                }
            }
            else
            {
                mEffectiveTheme = UI.Themes.Current;
                mThemeRenderer = mEffectiveTheme.FindRenderer(GetType());
            }
        }
        #endregion

        #endregion

        #region Layout

        #region Invalidate Layout
        /// <summary>
        /// Requests layout calculation to be performed again at next painting.
        /// </summary>
        public void InvalidateLayout()
        {
            mLayoutRequired = true;
            Invalidate();
        }
        #endregion

        #region On Calculate Layout
        /// <summary>
        /// Perform layout calculations neccessary for this control.
        /// </summary>
        protected virtual void OnCalculateLayout()
        {

        }
        #endregion

        #region Calculate Layout
        /// <summary>
        /// Perform layout calculations neccessary for this control.
        /// </summary>
        private void CalculateLayout()
        {
            OnCalculateLayout();
            mLayoutRequired = false;
        }
        #endregion

        #endregion

        #region Double - Buffering
        /// <summary>
        /// The bitmap that stores this control's painting.
        /// </summary>
        Bitmap mControlBitmap = null;
        /// <summary>
        /// The bitmap that stores this control's parent painting in the area occupied by this control.
        /// </summary>
        Bitmap mBackgroundBitmap = null;
        /// <summary>
        /// Graphics association for mControlBitmap.
        /// </summary>
        Graphics mControlCanvas = null;
        /// <summary>
        /// Graphics association for mBackgroundBitmap.
        /// </summary>
        Graphics mBackgroundCanvas = null;
        /// <summary>
        /// Whether buffers should be recreated.
        /// </summary>
        bool mBufferPreparationRequired = true;

        #region Prepare Buffers
        /// <summary>
        /// Recreate buffer bitmaps
        /// </summary>
        void PrepareBuffers()
        {
            int w = Width;
            int h = Height;

            if (mCacheBackground)
            {
                mBackgroundBitmap = new Bitmap(w, h, PixelFormat.Format32bppPArgb);
                mBackgroundCanvas = Graphics.FromImage(mBackgroundBitmap);
            }
            #region Defensive tracing
            else
            {
                //background caching is not required
            }
            #endregion

            mControlBitmap = new Bitmap(w, h, PixelFormat.Format32bppPArgb);
            mControlCanvas = Graphics.FromImage(mControlBitmap);
        }
        #endregion

        #region Dispose Buffers
        void DisposeBuffers()
        {
            if (mBackgroundCanvas != null)
            {
                mBackgroundCanvas.Dispose();
                mBackgroundCanvas = null;
            }
            #region Defensive tracing
            else
            {
                //null, no need to dispose
            }
            #endregion

            if (mBackgroundBitmap != null)
            {
                mBackgroundBitmap.Dispose();
                mBackgroundBitmap = null;
            }
            #region Defensive tracing
            else
            {
                //null, no need to dispose
            }
            #endregion

            if (mControlCanvas != null)
            {
                mControlCanvas.Dispose();
                mControlCanvas = null;
            }
            #region Defensive tracing
            else
            {
                //null, no need to dispose
            }
            #endregion

            if (mControlBitmap != null)
            {
                mControlBitmap.Dispose();
                mControlBitmap = null;
            }
            #region Defensive tracing
            else
            {
                //null, no need to dispose
            }
            #endregion
        }
        #endregion

        #endregion

        #region On Resize
        /// <summary>
        /// This method is raised when the control's sizes are changed.
        /// </summary>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            mBufferPreparationRequired = true;
            mRedrawBackground = true;
            Invalidate();
        }
        #endregion

        #region On Move
        /// <summary>
        /// This method is raised when the control's positions are changed.
        /// </summary>
        protected override void OnMove(EventArgs e)
        {
            base.OnMove(e);
            mRedrawBackground = true;
            Invalidate();
        }
        #endregion

        #region Set Default Styles
        /// <summary>
        /// Setting up default styles using SetStyle()
        /// </summary>
        protected void SetDefaultStyles()
        {
            this.SetStyle(ControlStyles.ResizeRedraw, true);
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.UserPaint, true);
        }
        #endregion

        #region Dispose
        /// <summary>
        /// Clean up resources.
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            DisposeBuffers();
            base.Dispose(disposing);
        }
        #endregion

        #region IControl Members

        #region Render
        /// <summary>
        /// Render this control on the provided graphics canvas.
        /// </summary>
        /// <param name="canvas">The canvas to render on to.</param>
        /// <param name="clip">The portion of the control to be rendered.</param>
        public void Render(Graphics canvas, Rectangle clip, bool includeBackground)
        {
            throw new NotSupportedException();
        }
        #endregion

        #endregion

        #region ISupportTransparency Members

        #region Invalidate Transparency
        /// <summary>
        /// Signify that this controls should invalidate its background for transparency consistent.
        /// </summary>
        void ISupportTransparency.InvalidateTransparency()
        {
            mRedrawBackground = true;
            InvalidateChildrenTransparency(this);
        }

        void InvalidateChildrenTransparency(Control con)
        {
            foreach (Control child in con.Controls)
            {
                if (child is ISupportTransparency) ((ISupportTransparency)child).InvalidateTransparency();
                else InvalidateChildrenTransparency(child);
            }
        }
        #endregion

        #endregion

        #region Constructors
        /// <summary>
        /// Create a new instance.
        /// </summary>
        public ForcedPaintingControl()
        {
            mDesignMode = LicenseManager.UsageMode == LicenseUsageMode.Designtime;
            mInvalidateHandler = new EventHandler(DoInvalidate);
            SetDefaultStyles();
        }
        #endregion
    }
}